#include "server.h"

#define SIM_RATE_IN_MS 20 // 10=100hz; 100=10hz;
#define NET_RATE_IN_MS 20 // goal=100ms w/ client prediction smoothing

Server::Server(QObject *parent) :
   QObject(parent)
{
   mGame = 0;
}


Server::Server(const QString hostIP, const int hostPort)
{
   mPort = hostPort;
   mClientPort = hostPort+1;
   mPlayerIDGen = 0;
   mLastNetTime = QTime::currentTime();
   mBlockSize = 0;

   mTcpServer = new QTcpServer(this);
   connect(mTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
   //mTcpServer->listen(QHostAddress::Any, hostPort);
   mTcpServer->listen(QHostAddress(hostIP), hostPort);

   mTimer = new QTimer(this);
   connect(mTimer, SIGNAL(timeout()), this, SLOT(onTimer()));
   mTimer->setInterval(SIM_RATE_IN_MS);

   mGame = new Game();
   mGame->setCurrentMap("./res/maps/map.xml");    //FIXME: Set default
   mGame->server = this;

   qDebug() << "Server::Started listening:" << hostIP << hostPort;
}


Server::~Server()
{
   qDebug() << "~Server";
   if (mGame)
      delete mGame;

   foreach (Player *p, mPlayer)
   {
      if (p->socket)
      {
         p->disconnect(); // maybe deleteLater?
         delete p->socket;
      }
      delete p;
   }

   if (mTimer)
   {
      mTimer->stop();
      delete mTimer;
   }

   if (mTcpServer)
   {
      mTcpServer->close();
      delete mTcpServer;
   }
}

QTime Server::currentTime()
{
   return QTime::currentTime();
}


void Server::sendMapChange(const QString &filename, QTcpSocket *socket)
{
   qDebug() << "Server::sendMapChange file=" << filename;
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);
   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_GAME_MAPCHANGE
      << filename;
   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   if (socket!=0)
   {
      socket->write(data);
   }
   else
   {
      // Send to all clients
      foreach (Player *p, mPlayer)
         p->socket->write(data);
   }
}


void Server::sendEntityUpdates(QTcpSocket *socket)
{
   //qDebug() << "Server::sendEntityUpdates";
   Entity::EntityListType entities = mGame->entityManager.listByType();

   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);

   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_UPDATES
      << (qint32)0;

   int count = 0;
   foreach(Entity *e, entities)
   {
      if (e->active() && e->updated())
      {
         count++;
         ds << e->id()
            << e->position().x() << e->position().y()
            << e->velocity().x() << e->velocity().y()
            << e->rotation();
         e->setUpdated(false);
      }
   }


   // Put sent entity count at front of msg
   ds.device()->seek(10);
   ds << (qint32)count;
   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   if (count > 0)
   {
      if (socket)
      {
         socket->write(data);
      }
      else
      {
         // Send to all clients
         foreach (Player *p, mPlayer)
            p->socket->write(data);
      }
   }
}


void Server::sendEntityAdd(Entity *entity, QTcpSocket *socket)
{
   if (!entity)
      return;

   qDebug() << "Server::sendEntityAdd entity=" << entity->id() << "squadID=" << entity->squadID() << "type=" << entity->type();
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);
   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_ADD;

   entity->marshall(EVENT_ENTITY_ADD, ds);

   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   // Send to client
   if (socket)
   {
      socket->write(data);
   }
   else // to everyone
   {
      foreach (Player *p, mPlayer)
         p->socket->write(data);
   }
}


void Server::sendEntitySquadAddToEmitter(const quint32 emitterID, const EntitySquadType &squad)
{
   qDebug() << "Server::sendEntitySquadAddToEmitter emitter=" << emitterID << "squad=" << squad.id;
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);

   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_SQUAD_ADD_TO_EMITTER
      << (quint32)emitterID
      << (quint32)squad.id
      << (quint32)squad.size
      << (qint32)squad.stats.kind
      << (qint32)squad.stats.size
      << squad.stats.color;

   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   // Send to all clients
   foreach (Player *p, mPlayer)
      p->socket->write(data);
}


void Server::sendEntitySquadRemoveFromEmitter(const quint32 emitterID, const quint32 squadID)
{
   qDebug() << "Server::sendEntitySquadRemoveFromEmitter emitter=" << emitterID << "squad=" << squadID;
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);

   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_SQUAD_REMOVE_FROM_EMITTER
      << (quint32)emitterID
      << (quint32)squadID;

   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   // Send to all clients
   foreach (Player *p, mPlayer)
      p->socket->write(data);
}


void Server::sendEntityRemove(const quint32 id)
{
   qDebug() << "Server::sendEntityRemove id=" << id;
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);

   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_REMOVE
      << (quint32)id;

   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   // Send to all clients
   foreach (Player *p, mPlayer)
      p->socket->write(data);
}

void Server::sendEntitySquadRemove(const quint32 squadID, const quint32 playerID)
{
   qDebug() << "Server::sendEntitySquadRemove squadID=" << squadID;
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);

   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_SQUAD_REMOVE
      << (quint32)squadID;

   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   Player *p = mPlayer.value(playerID);
   if (p)
      p->socket->write(data);
   //foreach (Player *p, mPlayer)
     // p->socket->write(data);
}


void Server::sendEntityAttack(const quint32 attackerID, const quint32 targetID, const qint32 damage)
{
   qDebug() << "Server::sendEntityAttack atk=" << attackerID << "targ=" << targetID << "dmg=" << damage;
   QByteArray data;
   QDataStream ds(&data, QIODevice::WriteOnly);

   ds << (quint16)0
      << (qint32)NET_MSG_GAME_EVENT
      << (qint32)EVENT_ENTITY_ATTACK
      << (quint32)attackerID
      << (quint32)targetID
      << (qint32)damage;

   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   // Send to all clients
   foreach (Player *p, mPlayer)
      p->socket->write(data);
}

/*
void Server::sendGameEvents()
{
   while (!mGame->events().isEmpty())
   {
      Event *event = mGame->events().takeFirst();
      QByteArray data;
      QDataStream ds(&data, QIODevice::WriteOnly);

      ds << (quint16)0
         << (qint32)NET_MSG_GAME_EVENT
         << (qint32)event->type();
      event->dataStream(ds);

      qDebug() << "Server::sendGameEvents type=" << event->type();

      // Send to all clients
      foreach (Player *p, mPlayer)
         p->socket->write(data);
      delete event;
   }
}
*/

void Server::onTimer()
{
   QTime time = QTime::currentTime();
   qreal timeDelta = mLastTime.msecsTo(time)*0.001;

// qDebug() << "Server::onTimer timeDelta=" << timeDelta;
   if (mGame)
   {
      mGame->tick(timeDelta);

      // Send update to clients
      if (mLastNetTime.msecsTo(time) >= NET_RATE_IN_MS)
      {
//       qDebug() << "Server::onTimer netTimeDelta=" << (mLastNetTime.msecsTo(mTime)*0.001);
         sendEntityUpdates();
         mLastNetTime = time;
      }
      //sendGameEvents();


      mGame->removeInactiveEntities();
   }
   mLastTime = time;
}


void Server::onData()
{
   QTcpSocket *socket = ((QTcpSocket*)sender());
   QDataStream data(socket);
   //data.setVersion(QDataStream::Qt_4_0);
   //qDebug() << "Server::onData: from:" << socket->localAddress().toString();

   while (socket->bytesAvailable())
   {
      // Try and grab the msg size first, then we'll wait for
      // the whole msg...
      if (mBlockSize == 0)
      {
         if (socket->bytesAvailable() < (int)sizeof(quint16))
         {
            //qDebug() << "Server::onData:waiting for blocksize";
            return;
         }
         data >> mBlockSize;
      }

      // Still don't have all the bytes... wait for more
      if (socket->bytesAvailable() < mBlockSize)
      {
         //qDebug() << "Server::onData:waiting for more data";
         return;
      }

      qint32 msgType = 0;
      data >> msgType;
      //qDebug() << "Server::onData:msgType:" << msgType;
      switch (msgType)
      {
      case NET_MSG_USER_EVENT: processUserEvent(data); break;
      default:
         qDebug() << "Server::onData: Invalid msgType:" << msgType;
         break;
      }

      // Reset block size
      mBlockSize = 0;
   }
}


void Server::onNewConnection()
{
   QTcpSocket *clientConnection = mTcpServer->nextPendingConnection();
   connect(clientConnection, SIGNAL(readyRead()), this, SLOT(onData()));
   connect(clientConnection, SIGNAL(disconnected()), this, SLOT(onDisconnect()));

   bool sendAck = false;
   //if (mPlayer.size() < 2) // FIXME: mark +2 as observer?
   {
      mPlayerIDGen++;
      mPlayer[mPlayerIDGen] = new Player(mPlayerIDGen, clientConnection);
      sendAck = true;
      qDebug() << "Server::OnNewConnection: welcome player" << mPlayerIDGen;
   }

   // Response to client...
   QByteArray data;
   QDataStream ds(&data, QIODevice::ReadWrite);
   ds << (quint16)0
      << (qint32)NET_MSG_JOIN
      << (bool)sendAck
      << (quint32)mPlayerIDGen;
   // Put msg size at the front of msg
   ds.device()->seek(0);
   ds <<(quint16)(data.size() - sizeof(quint16));

   clientConnection->write(data);

   // FIXME: send current map
   sendMapChange(mGame->currentMap(), clientConnection);

   // Send add of all entities
   Entity::EntityListType entities = mGame->entityManager.listByType();
   foreach (Entity *e, entities)
      sendEntityAdd(e, clientConnection);
}


void Server::onDisconnect()
{
   qDebug() << "Server::onDisconnect";
   QTcpSocket *socket = ((QTcpSocket*)sender());

   int id = -1;
   foreach(Player *p, mPlayer)
   {
      if (p->socket && socket == p->socket)
      {
         id = p->id;
         p->socket->deleteLater();
         p->deleteLater();
         break;
      }
   }

   if (id>=0)
   {
      mPlayer.remove(id);
      qDebug() << "Server::onDisconnect: removed pid=" << id;
   }
}


void Server::processUserEvent(QDataStream &data)
{
   qint32 userEventType=0;
   quint32 playerID=0;

   data >> userEventType >> playerID;

   qDebug() << "Server::processUserEvent pid=" << playerID << "event=" << userEventType;

   if (!mPlayer.contains(playerID))
   {
      qDebug() << "Server::processUserEvent:Invalid pid:" << playerID;
      return;
   }

   switch(userEventType)
   {
   case EVENT_USER_READY: processUserReady(data, playerID); break;
   case EVENT_ENTITY_ADD: processEntityAdd(data, playerID); break;
   case EVENT_ENTITY_SQUAD_ADD: processEntitySquadAdd(data, playerID); break;
   case EVENT_ENTITY_SQUAD_ADD_TO_EMITTER: processEntitySquadAddToEmitter(data, playerID); break;
   case EVENT_ENTITY_SQUAD_ID_REQ: processEntitySquadIDRequest(data, playerID); break;
   default: break;
   }
}


void Server::processUserReady(QDataStream &data, quint32 playerID)
{
   bool ready;
   data >> ready;

   qDebug() << "Server::processUserReady: ready=" << ready;

   // Check for game start

   bool shouldStart = true;
   /*
   // FIXME: Check for all players ready
   foreach(Player *p, mPlayer)
   {
      if (!p->isReady)
      {
         shouldStart = false;
         break;
      }
   }
*/

   if (mGame && shouldStart)
   {
      qDebug() << "Server::processUserReady: Starting sim!";
      //if (mGame)
         //mGame->start();
      mTimer->start();
   }
}


void Server::processEntitySquadAdd(QDataStream &data, quint32 playerID)
{
   if (mGame)
   {
      qint32 type=0;
      data >> type;

      qDebug() << "Server::processEntitySquadAdd type=" << type;

      Entity *e = 0;

      switch (type)
      {
      case ENTITY_UNIT: e = new Unit(); break;
      case ENTITY_TOWER: e = new Tower(); break;
      case ENTITY_UNITEMITTER: e = new UnitEmitter(); break;
      default: break;
      }
      e->demarshall(EVENT_ENTITY_ADD, data, playerID);
      mGame->addEntity(e);



      // Respond to clients
      if (e)
         sendEntityAdd(e);
   }
}

void Server::processEntityAdd(QDataStream &data, quint32 playerID)
{
   if (mGame)
   {
      qint32 type=0;
      data >> type;

      qDebug() << "Server::processEntityAdd type=" << type;

      Entity *e = 0;

      switch (type)
      {
      case ENTITY_UNIT: e = new Unit(); break;
      case ENTITY_TOWER: e = new Tower(); break;
      default: break;
      }
      e->demarshall(EVENT_ENTITY_ADD, data, playerID);
      mGame->addEntity(e);



      // Respond to clients
      if (e)
         sendEntityAdd(e);
   }
}

/*
void Server::processEntitySquadAdd(QDataStream &data, quint32 playerID)
{
   //qDebug() << "Server::processEntitySquadAdd";
   if (mGame)
   {
      qint32 type=0;
      quint32 squadSize=0, squadID=0;
      qreal posX=0.0,posY=0.0;
      EntityBaseStats stats;

      // Demarshall squad info
      data >> type >> stats.kind >> stats.size >> stats.color >> posX >> posY >> squadID >> squadSize;

      QPointF pos(posX, posY);
      stats.type = (ENTITY_TYPE)type;
      Entity::EntityListType list = mGame->addEntitySquad(playerID, squadID, stats, squadSize, pos);

      qDebug() << "Server::processEntitySquadAdd: squadID=" << squadID << "type=" << stats.type << "kind=" << stats.kind << "size=" << stats.size << "color=" << stats.color << "squadSize=" << squadSize << "pos=" << pos;

      // Response to clients...
      foreach (Entity *e, list)
         sendEntityAdd(e);
   }
}
*/

void Server::processEntitySquadAddToEmitter(QDataStream &data, quint32 playerID)
{
   if (mGame)
   {
      quint32 emitterID=0;
      data >> emitterID;

      Entity *e = mGame->entityManager.entityFromID(emitterID);
      if (e)
      {
         qDebug() << "Server::processEntitySquadAddToEmitter emitterID=" << emitterID;
         e->demarshall(EVENT_ENTITY_SQUAD_ADD_TO_EMITTER, data, playerID);
      }

   }
}


void Server::processEntitySquadIDRequest(QDataStream &data, quint32 playerID)
{
   if (mGame)
   {
      quint32 squadID = mGame->entityManager.uniqueSquadID();
      qDebug() << "Server::processEntitySquadIDRequest id=" << squadID;
      QByteArray gameEvent;
      QDataStream ds(&gameEvent, QIODevice::WriteOnly);
      ds << (quint16)0
         << (qint32)NET_MSG_GAME_EVENT
         << (qint32)EVENT_ENTITY_SQUAD_ID_REQ
         << (quint32)squadID;
      // Put msg size at the front of msg
      ds.device()->seek(0);
      ds <<(quint16)(gameEvent.size() - sizeof(quint16));

      Player *p = mPlayer.value(playerID);
      if (p)
         p->socket->write(gameEvent);
   }
}
